﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Server
{
    class ServerSession
    {
        private Socket m_Socket;
        private bool m_ThreadExit;
        private Thread m_Thread;
        private byte[] m_Buffer;
        private int m_BufferSize;
        private DateTime m_StartTime;

        public delegate void bytesReceivedNotifier(int count);
        public delegate void exitNotifier(ServerSession obj);

        bytesReceivedNotifier m_Notifier;
        exitNotifier m_ExitNotifier;

        public ServerSession(Socket socket, int bufferSize, bytesReceivedNotifier notify, exitNotifier exitNotify)
        {
            m_Notifier = notify;
            m_ExitNotifier = exitNotify;
            m_Socket = socket;
            m_BufferSize = bufferSize;
            m_StartTime = DateTime.MinValue;
            m_Buffer = new byte[m_BufferSize];
        }

        public void Start()
        {
            m_ThreadExit = false;
            m_Thread = new Thread(this.threadFn);
            m_Thread.Start();
        }

        public void Stop()
        {
            m_ThreadExit = true;
            m_Thread.Join();
        }

        private void threadFn()
        {
            int received;
            try
            {
                while (true)
                {
                    if (m_ThreadExit)
                        break;

                    received = m_Socket.Receive(m_Buffer, m_BufferSize, SocketFlags.None);
                    m_Notifier(received);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Connection close");
            }
            Console.WriteLine("Server Session thread terminated");
            m_Socket.Close();
            m_ExitNotifier(this);
        }
    }

    class ConnectionServer
    {
        private Socket m_Socket;
        private bool m_ThreadExit;
        private Thread m_Thread;
        private int m_SessionBufferSize;
        private List<ServerSession> m_Sessions;
        private int m_Port;
        private IPEndPoint m_LocalEndPoint;
        private long m_TotalBytesReceived;
        private DateTime m_StartTime;

        public ConnectionServer(int port, int sessionBufferSize)
        {
            m_Port = port;
            m_SessionBufferSize = sessionBufferSize;
            m_Sessions = new List<ServerSession>();

            IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
            IPAddress ipAddress = ipHostInfo.AddressList[0];
            // IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
            m_LocalEndPoint = new IPEndPoint(ipAddress, m_Port);

            // Create a TCP/IP socket.
            m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            m_Socket.Bind(m_LocalEndPoint);
            m_Socket.Listen(10);

            Console.WriteLine("IpAddress: " + ipAddress.ToString());
            Console.WriteLine("Port     : " + port);
            Console.WriteLine("BuffSize : " + m_SessionBufferSize);
        }

        public void Start()
        {
            m_ThreadExit = false;
            m_Thread = new Thread(this.threadFn);
            m_Thread.Start();
        }

        public void Stop()
        {
            foreach(ServerSession s in m_Sessions)
                s.Stop();

            m_ThreadExit = true;
            m_Thread.Join();
        }

        public void exitNotifier(ServerSession obj)
        {
            m_Sessions.Remove(obj);
        }

        public void bytesCounter(int count)
        {
            m_TotalBytesReceived += count;
        }

        public void ResetStatistics()
        {
            m_StartTime = DateTime.Now;
            m_TotalBytesReceived = 0;
        }

        public void PrintStats()
        {
            Console.WriteLine("\nTotal byterate   : " + m_TotalBytesReceived/(DateTime.Now - m_StartTime).TotalSeconds);
            Console.WriteLine("TotalConnections : " + m_Sessions.Count);
        }

        private void threadFn()
        {

            while (true)
            {
                if (m_ThreadExit)
                    break;

                Console.WriteLine("Waiting for connection");

                Socket s = m_Socket.Accept();
                Console.WriteLine("Connected");
                ServerSession session = new ServerSession(s, m_SessionBufferSize, bytesCounter, exitNotifier);
                m_Sessions.Add(session);
                session.Start();

            }
            Console.WriteLine("Server thread terminated");
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 1)
            {
                Console.WriteLine("Usage:");
                Console.WriteLine("Server.exe <port> [session buffer size]");
                return;
            }

            string port = args[0];
            
            string buffSize = "4096";
            if(args.Length > 1)
                buffSize = args[1];

            try
            {
                ConnectionServer server = new ConnectionServer(int.Parse(port), int.Parse(buffSize));
                server.Start();                
                while (true)
                {
                    Console.WriteLine("s - get Server statistics");
                    Console.WriteLine("r - reset Server statiscics");
                    Console.WriteLine("q - quit Server");
                    char ch = Console.ReadKey().KeyChar;
                    if (ch == 's')
                    {
                        server.PrintStats();
                    }
                    if (ch == 'r')
                    {
                        server.ResetStatistics();
                    }
                    if (ch == 'q')
                    {
                        server.Stop();
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            Console.ReadKey();
        
        }
    }
}
